# -*- coding = utf-8 -*-
import copy
import math
import random
import numpy as np
from numpy import array
import pandas as pd
import matplotlib.pyplot as plt
import pylab as mpl  # import matplotlib as mpl
from scipy import stats
import time


def fitness_single(pop_list, poplenth):
    sum = 0
    for i in range(poplenth // 4):

        bits = list(pop_list[4 * i:4 * (i + 1)])

        if (bits == A):
            sum = sum + 28
        elif (bits == B):
            sum = sum + 26
        elif (bits == C):
            sum = sum + 24
        elif (bits == D):
            sum = sum + 18
        elif (bits == E):
            sum = sum + 22
        elif (bits == F):
            sum = sum + 6
        elif (bits == G):
            sum = sum + 14
        elif (bits == H):
            sum = sum + 0
        elif (bits == I):
            sum = sum + 20
        elif (bits == J):
            sum = sum + 12
        elif (bits == K):
            sum = sum + 10
        elif (bits == L):
            sum = sum + 2
        elif (bits == M):
            sum = sum + 8
        elif (bits == N):
            sum = sum + 4
        elif (bits == O):
            sum = sum + 6
        elif (bits == P):
            sum = sum + 30

    # print("fitness")
    return sum  # 返回函数值


def fitness_multiple(pop_list, popsize, poplenth):
    fitness_list = []
    for i in range(popsize):
        sum = 0
        for j in range(poplenth // 4):
            bits = list(pop_list[i][4 * j:4 * (j + 1)])
            if (bits == A):
                sum = sum + 28
            elif (bits == B):
                sum = sum + 26
            elif (bits == C):
                sum = sum + 24
            elif (bits == D):
                sum = sum + 18
            elif (bits == E):
                sum = sum + 22
            elif (bits == F):
                sum = sum + 6
            elif (bits == G):
                sum = sum + 14
            elif (bits == H):
                sum = sum + 0
            elif (bits == I):
                sum = sum + 20
            elif (bits == J):
                sum = sum + 12
            elif (bits == K):
                sum = sum + 10
            elif (bits == L):
                sum = sum + 2
            elif (bits == M):
                sum = sum + 8
            elif (bits == N):
                sum = sum + 4
            elif (bits == O):
                sum = sum + 6
            elif (bits == P):
                sum = sum + 30

        fitness_list.append(sum)
    # print("fitness")
    return fitness_list


def cross_over(old_chrom, parents_list, cross_rate, popsize, poplenth):
    if np.random.rand() < cross_rate:
        # 从parents_list中随机选一条染色体
        n = np.random.randint(0, popsize, size=1)
        # parents_list_chrom_select = parents_list[chrom_select]
        # 从0-99位基因随机生成二个切点
        cut_point1 = int(np.random.randint(0, poplenth, size=1))
        cut_point2 = int(np.random.randint(0, poplenth, size=1))
        if cut_point1 < cut_point2:
            old_chrom[cut_point2 - cut_point1:poplenth - cut_point1] = parents_list[n, cut_point2:]
        else:
            old_chrom[cut_point1 - cut_point2:poplenth - cut_point2] = parents_list[n, cut_point1:]
    # print("cross_over")
    return old_chrom


def mutation(child_i, popsize, poplenth, mutation_rate, ):
    # -----------------1.变异策略---------------------
    for i in range(poplenth):  # 遍历染色体的每一位
        if np.random.rand() < mutation_rate:  # 对每一个位置比较是否小于变异率
            # 随机选择一个变异位置
            # mutation_point = np.random.randint(0, poplenth - 1)
            # pop_list[mutation_point] = 1 - pop_list[mutation_point]  # 变异取反
            if child_i[i] == 0:
                child_i[i] = 1
            if child_i[i] == 1:
                child_i[i] = 0
    # -----------------1.变异策略完成---------------------
    return child_i


def select(pop_list, fitness, popsize):
    idx = np.random.choice(np.arange(popsize), size=popsize, replace=True, p=fitness / fitness.sum())

    # print("select")
    return pop_list[idx]


if __name__ == "__main__":
    # 开始时间
    start_time = time.time()
    # --------------设置汉字格式-----------
    # 常见的无衬线字体有 Trebuchet MS, Tahoma, Verdana, Arial, Helvetica,SimHei 中文的幼圆、隶书等等
    mpl.rcParams['font.sans-serif'] = ['FangSong']  # 指定默认字体
    mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
    # 保存到Excel
    writer = pd.ExcelWriter(r"C:\Users\Venus\Desktop\Pycharm_Workspace\venv\智能优化方法\GA实验.xlsx")
    # solutionTime_pd = pd.DataFrame(solutionTime)
    # solutionTime_pd.to_excel(writer, sheet_name='工作用时')
    # writer.save()
    # writer.close()

    # ---------------1.参数设置-----------

    itera_max = 500  # 最大迭代次数

    crossover_rate = 0.66  # 交叉率
    mutation_rate = 0.00066  # 变异率
    popsize = 200  # 有100个基因,【行】，种群大小
    poplenth = 100  # 每个基因长度为100位，0-99，由25个4阶欺骗函数组成，【列】

    # new_pop_list = []
    # new_pop_value = []
    child_list = []
    old_pop_list = []
    fitness_list = []

    # 记录初始解
    # initial_solution = []
    # initial_solution_value = []
    # 记录求解历史
    history_solution = []
    history_solution_value = []

    now_best_solution = []  # 记录当前最优
    now_best_solution_value = []  # 记录当前最优

    best_solution = []  # 记录全局最优
    best_solution_value = []  # 记录全局最优

    # 进制编码
    A = [0, 0, 0, 0]
    B = [0, 0, 0, 1]
    C = [0, 0, 1, 0]
    D = [0, 0, 1, 1]
    E = [0, 1, 0, 0]
    F = [0, 1, 0, 1]
    G = [0, 1, 1, 0]
    H = [0, 1, 1, 1]
    I = [1, 0, 0, 0]
    J = [1, 0, 0, 1]
    K = [1, 0, 1, 0]
    L = [1, 0, 1, 1]
    M = [1, 1, 0, 0]
    N = [1, 1, 0, 1]
    O = [1, 1, 1, 0]
    P = [1, 1, 1, 1]

    # ---------------2.初始化-------------

    # 初始化种群矩阵pop_Matrix
    new_pop_list = np.random.randint(2, size=(popsize, poplenth))  # 产生初始种群
    # print(new_pop_list)
    # 计算初始解的适应值
    initial_solution = new_pop_list
    initial_solution_value = fitness_multiple(new_pop_list, popsize, poplenth)
    # 保存数据
    # initial_solution_pd = pd.DataFrame(initial_solution)
    # initial_solution_pd.to_excel(writer, sheet_name='初始解')
    # initial_solution_value_pd = pd.DataFrame(initial_solution_value)
    # initial_solution_value_pd.to_excel(writer, sheet_name='初始解值')

    print("初始染色体为：\n", initial_solution)
    print("初始函数值为：\n", initial_solution_value)

    history_solution.append(initial_solution)
    history_solution_value.append(initial_solution_value)

    now_best_solution.append(initial_solution)
    now_best_solution_value.append(initial_solution_value)

    best_solution.append(new_pop_list[initial_solution_value.index(max(initial_solution_value))].tolist())
    best_solution_value.append(max(initial_solution_value))
    # ---------------3.迭代-------------

    itera = 0
    best_itera = 0  # 记录最优解出现的次数
    while itera < itera_max:
        print("--------------->>开始")
        print("第", itera + 1, "次")

        # 1.计算适应值
        fitness_list = fitness_multiple(new_pop_list, popsize, poplenth)
        # 选择中的父代染色体编号列表
        # 2.选择
        select_list = select(new_pop_list, np.array(fitness_list), popsize)
        parents_list = select_list.copy()
        # print("parents_list\n", parents_list)
        for old_chrom in parents_list:
            # print("old_chrom", old_chrom)
            # 3.交叉
            child_i = cross_over(old_chrom, parents_list, crossover_rate, popsize, poplenth)
            # 4.变异
            child_i = mutation(child_i, popsize, poplenth, mutation_rate)
            # 5.计算单条染色体的适应度
            child_i_fitness = fitness_single(child_i, poplenth)
            # if child_i_fitness > np.median(fitness_list):  # 中位数
            if child_i_fitness > np.mean(fitness_list):  # 平均值
                parents_list[fitness_list.index(min(fitness_list)), :] = child_i
                fitness_list[fitness_list.index(min(fitness_list))] = child_i_fitness
        # 更新父代
        select_list = parents_list.copy()

        print("本次最佳值:", max(fitness_list))
        # 记录最优解
        if max(fitness_list) > best_solution_value[-1]:
            best_solution.append(select_list[fitness_list.index(max(fitness_list))].copy())
            best_solution_value.append(max(fitness_list))
            print("发现一个更优解---->,染色体为：", best_solution[-1], "，函数值为：", best_solution_value[-1])
            best_itera = itera + 1
        else:
            best_solution_value.append(best_solution_value[-1])
            best_solution.append(best_solution[best_solution_value.index(best_solution_value[-1])].copy())

        print_value = fitness_multiple(parents_list, popsize, poplenth)
        max_value_idx = np.argmax(print_value)
        # print("最优染色体为：", select_list[max_value_idx])
        # print("最优适应值为：", print_value[max_value_idx])
        fitness_list.clear()

        print("--------------->>结束第", itera + 1, "次")
        itera += 1

    end_time = time.time()  # 结束时间
    # ---------------4.画图-------------

    print("--------------实验结果---------------")
    print("实验用时： ", end_time - start_time, "秒")
    # 最优解
    print("最后得到的最优解为第", best_itera, "次", best_solution[-1])
    print("         最优函数值为：", best_solution_value[-1])

    # 画出最优解迭代
    best_Solution_Itera = []
    for i in range(len(best_solution)):
        best_Solution_Itera.append(i)

    plt.plot(best_Solution_Itera, best_solution_value, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.title('最优值历史')
    plt.xlabel('迭代次数')
    plt.ylabel('最优值')
    plt.show()
    # 保存数据
    best_solution_pd = pd.DataFrame(best_solution)
    best_solution_pd.to_excel(writer, sheet_name='最优解')

    best_solution_value_pd = pd.DataFrame(best_solution_value)
    best_solution_value_pd.to_excel(writer, sheet_name='最优解值')

    time_cost = []
    time_cost.append(end_time - start_time)
    time_pd = pd.DataFrame(time_cost)
    time_pd.to_excel(writer, sheet_name='求解时间')

    writer.save()
    writer.close()
